﻿using System;
using NUnit.Framework;
using NUnit.Framework.Constraints;
using SharpRomans.Support;

namespace SharpRomans.Tests.Support
{
	[TestFixture]
	public class RangeTester
	{
		#region ctor

		[Test]
		public void Ctor_Default_SetBoundsToDefault()
		{
			Assert.That(new Range<int>(), withDefaultBounds<int>());

			Assert.That(new Range<TimeSpan>(), withDefaultBounds<TimeSpan>());

			Assert.That(new Range<DateTimeOffset>(), withDefaultBounds<DateTimeOffset>());
		}

		private IResolveConstraint withDefaultBounds<T>()
		{
			return Has.Property("LowerBound").EqualTo(default(T))
				.And.Property("UpperBound").EqualTo(default(T));
		}

		[Test, SetCulture("es-ES")]
		public void Ctor_PoorlyConstructed_ThrowsException()
		{
			Assert.That(() => new Range<int>(5, 1), throwsOutOfRangeException(1, "1"));

			Assert.That(() => new Range<int>(-1, -5), throwsOutOfRangeException(-5, "-5"));

			Assert.That(() => new Range<TimeSpan>(3.Seconds(), 2.Seconds()), throwsOutOfRangeException(2.Seconds(), "00:00:02"));

			Assert.That(() => new Range<DateTimeOffset>(11.March(1977), 31.October(1952)), throwsOutOfRangeException(31.October(1952), "31/10/1952 0:00:00 +00:00"));
		}

		private IResolveConstraint throwsOutOfRangeException<T>(T value, string valueRepresentation)
		{
			return Throws.InstanceOf<ArgumentOutOfRangeException>()
				.With.Message.StringContaining(valueRepresentation)
				.And.Property("ActualValue").EqualTo(value);
		}

		[Test]
		public void Ctor_NicelyConstructed_NoException()
		{
			Assert.That(() => new Range<int>(1, 5), Throws.Nothing);

			Assert.That(() => new Range<int>(-5, -1), Throws.Nothing);

			Assert.That(() => new Range<TimeSpan>(2.Seconds(), 3.Seconds()), Throws.Nothing);

			Assert.That(() => new Range<DateTimeOffset>(31.October(1952), 11.March(1977)), Throws.Nothing);
		}

		[Test]
		public void Ctor_NicelyConstructed_SetsProperties()
		{
			var subject = new Range<char>('a', 'z');

			Assert.That(subject.LowerBound, Is.EqualTo('a'));
			Assert.That(subject.UpperBound, Is.EqualTo('z'));
		}

		#endregion

		#region Contains

		[Test]
		public void Contains_Integers_ContainedAndNotContained()
		{
			Assert.That(new Range<int>(1, 5).Contains(3), Is.True);
			Assert.That(new Range<int>(-5, -1).Contains(3), Is.False);
			Assert.That(new Range<int>(-5, -1).Contains(-4), Is.True);
			Assert.That(new Range<int>(-1, 1).Contains(0), Is.True);
			Assert.That(new Range<int>(-5, -1).Contains(3), Is.False);
		}

		[Test]
		public void Contains_InterpretesBounds_AsIfOpenRange()
		{
			Assert.That(new Range<int>(1, 5).Contains(1), Is.True);
			Assert.That(new Range<int>(1, 5).Contains(5), Is.True);
		}

		[Test]
		public void Contains_Dates_ContainedAndNotContained()
		{
			Range<DateTimeOffset> ww2Period = new Range<DateTimeOffset>(3.September(1939), 2.September(1945));
			Assert.That(ww2Period.Contains(1.January(1940)), Is.True);
			Assert.That(ww2Period.Contains(1.January(1980)), Is.False);
			Assert.That(ww2Period.Contains(2.September(1939).At(12, 59, 59, 999)), Is.False);
		}

		#endregion

		[Test, SetCulture("es-ES")]
		public void ToString_ContainsBounds()
		{
			Assert.That(new Range<int>(1, 5).ToString(), Is.EqualTo("[1..5]"));

			Assert.That(new Range<int>(-5, -1).ToString(), Is.EqualTo("[-5..-1]"));

			Assert.That(new Range<TimeSpan>(2.Seconds(), 3.Seconds()).ToString(), Is.EqualTo("[00:00:02..00:00:03]"));

			Assert.That(new Range<DateTimeOffset>(31.October(1952), 11.March(1977)).ToString(), Is.EqualTo("[31/10/1952 0:00:00 +00:00..11/03/1977 0:00:00 +00:00]"));
		}
	}
}
